﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using lpsolve55;
using Pdm.Commons;
using System.Diagnostics;

namespace Pdm.Solver
{
    public class Optimizer
    {
        internal static void calculateDea(Optimization problem)
        {
            int lp = constructDeaLPModel(problem);
            lpsolve.solve(lp);

            //set obj value
            problem.objectiveValue = lpsolve.get_objective(lp);

            //set dea weights
            double[] optimizedWeights = new double[lpsolve.get_Ncolumns(lp)];
            lpsolve.get_variables(lp, optimizedWeights);

            problem.deaWeights.Clear();
            for (int i = 0; i < optimizedWeights.Length; i++)
            {
                problem.deaWeights.Add(
                    new SpecificationInstance(problem.dmuList.coefficients[i], optimizedWeights[i]));
            }
        }
        internal static void calculatePdm(PdmOptimization problem)
        {
            int lp = constructPdmLPModel(problem);
            lpsolve.solve(lp);
            
            //set obj value
            problem.objectiveValue = lpsolve.get_objective(lp);

            //set pdm weights
            double[] optimizedWeights = new double[lpsolve.get_Ncolumns(lp)];
            lpsolve.get_variables(lp, optimizedWeights);

            problem.deaWeights.Clear();
            problem.pdmWeights.Clear();
            for (int i = 0; i < optimizedWeights.Length; i++)
            {
                problem.pdmWeights.Add(
                    new SpecificationInstance(problem.dmuList.coefficients[i], optimizedWeights[i]));
            }

            //set efficiency
            calculateEfficiency(problem);

            //set profiledistance
            calculateProfileDistance(problem);
        }
        private static void calculateEfficiency(PdmOptimization problem)
        {
            problem.efficiency = 0d;
            for (int j = problem.dmuList.getIDofFirstOutputCoeffiecient(); j < problem.dmuList.coefficients.Count; j++)
            {
                problem.efficiency += (problem.dmuList[problem.objectiveDmu.id].coefficients[j].value * 
                    problem.pdmWeights[j].value);
            }
        }
        private static void calculateProfileDistance(PdmOptimization problem)
        {
            problem.profileDistance = 0d;
            int firstOut = problem.dmuList.getIDofFirstOutputCoeffiecient();
            for (int j = firstOut; j < problem.dmuList.coefficients.Count; j++)
            {
                problem.profileDistance += Math.Abs(problem.directions[j].value *
                    (problem.pdmWeights[j].value -
                    (problem.urmWeights[j].value / problem.urmWeights[firstOut].value) *
                    problem.pdmWeights[firstOut].value));
            }
        }
        private static int constructPdmLPModel(PdmOptimization problem)
        {
            int lp;
            int j = 0;
            int Ncol = problem.dmuList.coefficients.Count;
            double[] objRow = new double[1 + Ncol];
            double[] denomRow = new double[1 + Ncol];
            Double[][] geConstraints = new Double[problem.dmuList.Count][];
            double[] tenRow = new double[1 + Ncol];
            double[] pdmRow = new double[1 + Ncol];

            for (int i = 0; i < geConstraints.Length; i++)
            {
                geConstraints[i] = new Double[1 + Ncol];
            }

            lp = lpsolve.make_lp(0, Ncol);

            if (lp == 0)
            {
                Console.WriteLine("Could not generate new LP model");
                return lp;
            }
            lpsolve.set_outputfile(lp, "E:\\result.txt");

            for (int i = 1; i <= Ncol; i++)
                lpsolve.set_col_name(lp, i, problem.dmuList.coefficients[i - 1].name);


            // add obj function and denominator equality constraint and GE constraint

            foreach (Dmu dmu in problem.dmuList)
            {
                j = 1;
                foreach (SpecificationInstance specInst in problem.dmuList[dmu.id].coefficients)
                {
                    if (dmu.id == problem.objectiveDmu.id)
                    {
                        if (specInst.spec.type == Pdm.Commons.Type.Input)
                        {
                            denomRow[j] = 1;
                        }
                        else
                        {
                            objRow[j] = specInst.value - problem.alpha * problem.directions[j - 1].value;

                            int firstOut = problem.dmuList.getIDofFirstOutputCoeffiecient();
                            if ((j - 1) == firstOut)
                            {
                                for (int i = j - 1; i < problem.dmuList.coefficients.Count; i++)
                                {
                                    objRow[j] += problem.alpha * problem.directions[i].value * (problem.urmWeights[i].value / problem.urmWeights[j - 1].value);
                                }
                            }
                        }
                    }
                    switch (specInst.spec.type)
                    {
                        case Pdm.Commons.Type.Input:
                            geConstraints[dmu.id][j] = specInst.value;
                            break;
                        case Pdm.Commons.Type.Output:
                            geConstraints[dmu.id][j] = -1 * specInst.value;
                            break;
                        default:
                            break;
                    }
                    j++;
                }
            }
            lpsolve.set_obj_fn(lp, objRow);

            lpsolve.add_constraint(lp, denomRow, lpsolve.lpsolve_constr_types.EQ, 1);

            for (j = 0; j < problem.dmuList.Count; j++)
            {
                lpsolve.add_constraint(lp, geConstraints[j], lpsolve.lpsolve_constr_types.GE, 0);
            }

            //pdm constraints
            if (problem.alpha > 0d)
            {
                foreach (Specification spec in problem.dmuList.coefficients)
                {
                    if(spec.type == Pdm.Commons.Type.Output){

                        pdmRow = new double[1 + Ncol];
                        pdmRow[spec.id + 1] = problem.alpha * problem.directions[spec.id].value;
                        pdmRow[problem.dmuList.getIDofFirstOutputCoeffiecient() + 1] += -1 * problem.alpha * 
                            problem.directions[spec.id].value * 
                            (problem.urmWeights[spec.id].value / problem.urmWeights[problem.dmuList.getIDofFirstOutputCoeffiecient()].value);
                        lpsolve.add_constraint(lp, pdmRow, lpsolve.lpsolve_constr_types.GE, 0);
                    }
                }
            }


            j = 1;
            foreach (Specification spec in problem.dmuList.coefficients)
            {
                int i = 1;
                foreach (Specification rea in problem.dmuList.coefficients)
                {
                    if (spec.type != Pdm.Commons.Type.Input && rea.type != Pdm.Commons.Type.Input)
                    {
                        if (spec.id != rea.id)
                        {
                            tenRow = new double[1 + Ncol];
                            tenRow[j] = -0.1;
                            tenRow[i] = 1;
                            lpsolve.add_constraint(lp, tenRow, lpsolve.lpsolve_constr_types.GE, 0);
                        }
                    }
                    i++;
                }
                j++;
            }
            lpsolve.set_maxim(lp);
            lpsolve.print_lp(lp);
            return lp;
        }
        private static int constructDeaLPModel(Optimization problem)
        {
            int lp;
            int j = 0;
            int Ncol = problem.dmuList.coefficients.Count;
            double[] objRow = new double[1 + Ncol];
            double[] denomRow = new double[1 + Ncol];
            Double[][] geConstraints = new Double[problem.dmuList.Count][];
            double[] tenRow = new double[1 + Ncol];

            for (int i = 0; i < geConstraints.Length; i++)
            {
                geConstraints[i] = new Double[1 + Ncol];
            }

            lp = lpsolve.make_lp(0, Ncol);

            if (lp == 0)
            {
                Console.WriteLine("Could not generate new LP model");
                return lp;
            }
            lpsolve.set_outputfile(lp, "E:\\result.txt");

            for (int i = 1; i <= Ncol; i++)
                lpsolve.set_col_name(lp, i, problem.dmuList.coefficients[i - 1].name);


            // add obj function and denominator equality constraint and GE constraint

            foreach (Dmu dmu in problem.dmuList)
            {
                j = 1;
                foreach (SpecificationInstance specInst in problem.dmuList[dmu.id].coefficients)
                {
                    if (dmu.id == problem.objectiveDmu.id)
                    {
                        if (specInst.spec.type == Pdm.Commons.Type.Input)
                        {
                            denomRow[j] = 1;
                        }
                        else
                        {
                            objRow[j] = specInst.value;
                        }
                    }
                    switch (specInst.spec.type)
                    {
                        case Pdm.Commons.Type.Input:
                            geConstraints[dmu.id][j] = specInst.value;
                            break;
                        case Pdm.Commons.Type.Output:
                            geConstraints[dmu.id][j] = -1 * specInst.value;
                            break;
                        default:
                            break;
                    }
                    j++;
                }
            }
            lpsolve.set_obj_fn(lp, objRow);

            lpsolve.add_constraint(lp, denomRow, lpsolve.lpsolve_constr_types.EQ, 1);

            for (j = 0; j < problem.dmuList.Count; j++)
            {
                lpsolve.add_constraint(lp, geConstraints[j], lpsolve.lpsolve_constr_types.GE, 0);
            }

            j = 1;
            foreach (Specification spec in problem.dmuList.coefficients)
            {
                int i = 1;
                foreach (Specification rea in problem.dmuList.coefficients)
                {
                    if (spec.type != Pdm.Commons.Type.Input && rea.type != Pdm.Commons.Type.Input)
                    {
                        if (spec.id != rea.id)
                        {
                            tenRow = new double[1 + Ncol];
                            tenRow[j] = -0.1;
                            tenRow[i] = 1;
                            lpsolve.add_constraint(lp, tenRow, lpsolve.lpsolve_constr_types.GE, 0);
                        }
                    }
                    i++;
                }
                j++;
            }
            lpsolve.set_maxim(lp);
            lpsolve.print_lp(lp);
            return lp;
        }
    }
}
